import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Observable;

import common.Command;
import common.CommandType;

public class UDPServer extends Observable {
	DatagramSocket serverSocket;
	byte[] receiveData;
	byte[] sendData;
	private ServerState state;
	private String connectionKey;
	private int port;
	private boolean logged;
	

	public boolean isStarted() {
		return state.isStarted();
	}

	public UDPServer(int port, String connectionKey) {
		this.port = port;
		this.connectionKey = connectionKey;
		state = new ServerState();
		receiveData = new byte[1024];
		sendData = new byte[1024];
		logged = false;
	}

	public void setPort(int port) throws Exception {
		this.port = port;
		if (state.isStarted()) {
			restart();
		}
	}

	public void restart() throws Exception {
		if (state.isStarted()) {
			stop();
			Thread.sleep(10);
			start();
		}
	}

	public void start() throws Exception {
		try {
			serverSocket = new DatagramSocket(port);
			System.out.println("Server running on port: " + port);
			state.setStarted(true);
			Thread t = new Thread(new Runnable() {

				@Override
				public void run() {
					while (state.isStarted()) {
						try {
							DatagramPacket receivePacket = new DatagramPacket(
									receiveData, receiveData.length);
							serverSocket.receive(receivePacket);
							
							Command command = extractCommandFromDatagram(receivePacket);

							if (command != null) {
								if (logged
										&& command.getType().equals(
												CommandType.DISCONNECT)) {
									logged = false;
									setChanged();
									notifyObservers("DISCONNECTED");
								}

								if (logged) {
									CommunicationProtocol.perform(command);
								} else {

									logged = checkLogin(command);

									// Rispondo al Client
									sendAnswerToClient(receivePacket,
											new Command(CommandType.LOGIN,
													logged + "", ""));

									if (logged) {
										setChanged();
										notifyObservers("CONNECTED");
									}
								}
							}

						} catch (IOException e) {
							System.out
									.println("Can't accept connections. Socket has been closed");
						} catch (ClassNotFoundException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}

				}
			});

			t.start();
		} catch (Exception e) {
			throw new Exception(e);
		}

	}

	private Command extractCommandFromDatagram(DatagramPacket receivePacket)
			throws IOException, ClassNotFoundException {
		ObjectInputStream iStream = new ObjectInputStream(
				new ByteArrayInputStream(receivePacket.getData()));
		Command command = (Command) iStream.readObject();
		iStream.close();

		return command;
	}

	private void sendAnswerToClient(DatagramPacket receivedPacket,
			Command commandToSend) throws IOException {
		InetAddress IPAddress = receivedPacket.getAddress();
		int port = receivedPacket.getPort();

		serverSocket.send(createDatagram(commandToSend, IPAddress, port));
	}

	private DatagramPacket createDatagram(Command c, InetAddress address,
			int port) throws IOException {
		ByteArrayOutputStream bStream = new ByteArrayOutputStream();
		ObjectOutput oo = new ObjectOutputStream(bStream);
		oo.writeObject(c);
		oo.close();
		sendData = bStream.toByteArray();
		bStream.close();

		DatagramPacket sendPacket = new DatagramPacket(sendData,
				sendData.length, address, port);
		return sendPacket;
	}

	private boolean checkLogin(Command command) {
		if (command.getType().equals(CommandType.LOGIN)
				&& command.getPassword().equals(connectionKey))
			return true;
		else
			return false;
	}

	public void stop() {
		if (state.isStarted()) {
			state.setStarted(false);
			serverSocket.close();
			logged = false;
			setChanged();
			notifyObservers("DISCONNECTED");
			System.out.println("Socket Closed");
		}
	}

	public boolean isLogged(){
		return logged;
	}
}
